﻿namespace nGo.Web.Mvc

open System.ComponentModel.Composition
open System.ComponentModel.Composition.Hosting
open System.Data.Entity.Infrastructure
open System.Diagnostics
open System.Linq
open System.Web
open System.Web.Http
open System.Web.Mvc
open System.Web.Optimization
open System.Web.Routing

open MefContrib.Hosting.Conventions
open MefContrib.Hosting.Conventions.Configuration
open MefContrib.Web.Mvc

module public Mef = begin
    
    open Microsoft.Practices.ServiceLocation

    open nGo.Mef
    
    let internal DefaultContainerLifetimeModuleRegistration = nGo.SingletonAction((fun() -> CompositionContainerLifetimeHttpModule.Register()), false)

    /// Create MEF dependency resolver based on the contents of ~/bin.
    let internal buildMefBinDependencyResolver () = 
        // Create MEF catalog based on the contents of ~/bin.
        //
        // Note that any class in the referenced assemblies implementing in "IController"
        // is automatically exported to MEF. There is no need for explicit [Export] attributes
        // on ASP.NET MVC controllers. When implementing multiple constructors ensure that
        // there is one constructor marked with the [ImportingConstructor] attribute.
        let appRegistry = (new MefContrib.Web.Mvc.MvcApplicationRegistry()) :> obj :?> IPartRegistry<IContractService>
        let conventionCatalog = new ConventionCatalog([|appRegistry|])
        let binCatalog = new DirectoryCatalog("bin")
        let catalog = new AggregateCatalog(binCatalog, conventionCatalog)
        
        // Tell MVC3 to use MEF as its dependency resolver.
        let dependencyResolver = new CompositionDependencyResolver(catalog)

        let container = new CompositionContainer(catalog)
        let serviceLocator = new ServiceLocator(container) :> IServiceLocator
        let locator = nGo.Services.Locator(serviceLocator)

        nGo.Services.GlobalLocator.Value <- locator

        dependencyResolver

    let internal getMefControllerFactory (dependencyBuilder:IDependencyBuilder) =
        let controllerActivator = new CompositionControllerActivator(dependencyBuilder)
        new CompositionControllerFactory(controllerActivator)

    let internal getMefFilterProvider (dependencyBuilder:IDependencyBuilder) =
        new CompositionFilterAttributeFilterProvider(dependencyBuilder)

    let internal getMefModelValidatorProvider (dependencyBuilder:IDependencyBuilder) =
        new CompositionDataAnnotationsModelValidatorProvider(dependencyBuilder)
    
    let internal getMefModelBinderProvider (provider:ICompositionContainerProvider) =
        new CompositionModelBinderProvider(provider)

    type public ApplicationPreStart() =
        inherit nGo.Web.Mvc.ApplicationPreStart()

        static let mutable isPreStarted = false
        static let preStartLock = obj()

        static let init () = 
            Debug.WriteLine("nGo: ApplicationPreStart.Init()")

            let mef = ApplicationPreStart()

            mef.RegisterMefContainerLifetimeModule() |> ignore
        
            let dependencyResolver = mef.GetMefDependencyResolver()
        
            mef.RegisterDependencies(dependencyResolver) |> ignore

            let controllerFactory = mef.GetMefControllerFactory(dependencyResolver)
        
            mef.RegisterControllerFactory(controllerFactory) |> ignore
        
            let filterProvider = mef.GetMefFilterProvider(dependencyResolver)
        
            mef.RegisterFilterProvider(filterProvider) |> ignore
        
            let modelValidatorProvider = mef.GetMefModelValidatorProvider(dependencyResolver)
        
            mef.RegisterModelValidatorProvider(modelValidatorProvider) |> ignore
        
            let modelBinderProvider = mef.GetMefModelBinderProvider(dependencyResolver)
        
            mef.RegisterModelBinderProvider(modelBinderProvider) |> ignore

            // Note: RegisterBundleResolver() is not called. Could be a MefContrib thing in the future.

            true

        static member Init() =
            Debug.WriteLine("nGo: ApplicationPreStart.Init() initialized? {0}", isPreStarted)
            if not isPreStarted then
                lock preStartLock (fun() -> 
                    if not isPreStarted then
                        let initialized = init()
                        isPreStarted <- initialized)

        abstract member RegisterMefContainerLifetimeModule : unit -> bool
        default x.RegisterMefContainerLifetimeModule() = 
            Debug.WriteLine("nGo: ApplicationPreStart.RegisterMefContainerLifetimeModule()")
            DefaultContainerLifetimeModuleRegistration.Invoke()

        abstract member GetMefDependencyResolver : unit -> CompositionDependencyResolver
        default x.GetMefDependencyResolver() = 
            Debug.WriteLine("nGo: ApplicationPreStart.GetMefDependencyResolver()")
            buildMefBinDependencyResolver()

        abstract member GetMefControllerFactory : IDependencyBuilder -> CompositionControllerFactory
        default x.GetMefControllerFactory(dependencyBuilder:IDependencyBuilder) = 
            Debug.WriteLine("nGo: ApplicationPreStart.GetMefControllerFactory()")
            getMefControllerFactory dependencyBuilder

        abstract member GetMefFilterProvider : IDependencyBuilder -> CompositionFilterAttributeFilterProvider
        default x.GetMefFilterProvider(dependencyBuilder:IDependencyBuilder) = 
            Debug.WriteLine("nGo: ApplicationPreStart.GetMefFilterProvider()")
            getMefFilterProvider dependencyBuilder

        abstract member GetMefModelValidatorProvider : IDependencyBuilder -> CompositionDataAnnotationsModelValidatorProvider
        default x.GetMefModelValidatorProvider(dependencyBuilder:IDependencyBuilder) = 
            Debug.WriteLine("nGo: ApplicationPreStart.GetMefModelValidatorProvider()")
            getMefModelValidatorProvider dependencyBuilder

        abstract member GetMefModelBinderProvider : ICompositionContainerProvider -> CompositionModelBinderProvider
        default x.GetMefModelBinderProvider(provider:ICompositionContainerProvider) = 
            Debug.WriteLine("nGo: ApplicationPreStart.GetMefModelBinderProvider()")
            getMefModelBinderProvider provider
end